/* *****************************************************************************
 * JFab (http://code.google.com/p/jfab)
 * Copyright (c) 2011 JFab.org
 * Admin jfab@jeffreythompson.net
 *
 * See the file "LICENSE.txt" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 * *****************************************************************************/

package org.jfab.core.factory;

import java.util.List;
import java.util.Map;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.javaruntype.exceptions.TypeRecognitionException;
import org.javaruntype.type.Type;
import org.javaruntype.type.Types;
import org.jfab.core.adapter.Adapter;
import org.jfab.core.model.CGType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Provides a code generator type factory.
 */
public final class DefaultCGTypeFactory implements CGTypeFactory
{
    /** Logger. */
    private static final Logger LOGGER = LoggerFactory
            .getLogger(DefaultCGTypeFactory.class);

    /** Code generator type adapter. */
    private final Adapter<CGType> _cgTypeAdapter;

    /**
     * Construct this object with the given parameter.
     * 
     * @param cgTypeAdapter Code generator type adapter.
     */
    public DefaultCGTypeFactory(final Adapter<CGType> cgTypeAdapter)
    {
        _cgTypeAdapter = cgTypeAdapter;
    }

    @Override
    public final CGType createType(final Class<?> aClass)
    {
        Validate.notNull(aClass, "aClass is null");

        final Type<?> type = Types.forClass(aClass);

        return createType(type);
    }

    @Override
    public final CGType createType(final java.lang.reflect.Type type)
    {
        CGType answer = null;

        if (type != null)
        {
            String nameString = null;

            if (type instanceof Class)
            {
                nameString = ((Class<?>)type).getName();
            }
            else
            {
                nameString = type.toString();
            }

            answer = _cgTypeAdapter.unmarshal(nameString);
        }

        return answer;
    }

    @Override
    public final CGType createType(final String name)
    {
        return createType((String)null, name, null);
    }

    @Override
    public final CGType createType(final String packageName,
            final Map<String, CGType> importMap, final String className)
    {
        CGType answer = null;

        if (StringUtils.isEmpty(packageName) && (importMap == null))
        {
            answer = createType(className);
        }
        else if (StringUtils.isNotEmpty(className))
        {
            String myCodeFragment = className;
            final int index0 = myCodeFragment.indexOf('<');

            if (index0 >= 0)
            {
                final int index1 = myCodeFragment.indexOf('>');
                String text0 = StringUtils.substringBetween(myCodeFragment,
                        "<", ">");
                text0 = text0.trim();

                final String[] parts = text0.split(",");

                final StringBuilder sb = new StringBuilder();
                final int size = parts.length;

                for (int i = 0; i < size; i++)
                {
                    final CGType type = createTypeHelper(packageName,
                            importMap, parts[i]);

                    if (type != null)
                    {
                        final String part = type.getName();
                        sb.append(part);
                    }
                    else
                    {
                        sb.append("?");
                    }

                    if (i < (size - 1))
                    {
                        sb.append(", ");
                    }
                }

                myCodeFragment = myCodeFragment.substring(0, index0 + 1)
                        + sb.toString() + myCodeFragment.substring(index1);
                answer = createType(myCodeFragment);
            }
            else
            {
                answer = createTypeHelper(packageName, importMap, className);
            }
        }

        return answer;
    }

    @Override
    public final CGType createType(final String packageName, final String name)
    {
        return createType(packageName, name, null);
    }

    @Override
    public final CGType createType(final String packageName, final String name,
            final List<CGType> typeParameters)
    {
        Validate.notEmpty(name, "name is empty");

        final CGType answer = new CGType();

        answer.setPackageName(packageName);
        answer.setName(name);

        if (CollectionUtils.isNotEmpty(typeParameters))
        {
            answer.getTypeParameters().addAll(typeParameters);
        }

        return answer;
    }

    @Override
    public final CGType createType(final Type<?> type)
    {
        CGType answer = null;

        if (type != null)
        {
            final String nameString = type.getName();
            answer = _cgTypeAdapter.unmarshal(nameString);
        }

        return answer;
    }

    /**
     * @param packageName Package name.
     * @param importMap Map of simple type name to type.
     * @param className Class name.
     * 
     * @return a new type.
     */
    private CGType createTypeHelper(final String packageName,
            final Map<String, CGType> importMap, final String className)
    {
        CGType answer = null;

        String myClassName = className.trim();

        if (!myClassName.contains("."))
        {
            CGType cgType = null;

            if (importMap != null)
            {
                cgType = importMap.get(myClassName);
            }

            if (cgType == null)
            {
                if (StringUtils.isNotEmpty(packageName))
                {
                    try
                    {
                        final String myClassName2 = packageName + "."
                                + myClassName;
                        final Type<?> type = Types.forName(myClassName2);

                        if (type != null)
                        {
                            cgType = createType(type);
                        }
                    }
                    catch (final TypeRecognitionException e)
                    {
                        if (LOGGER.isDebugEnabled())
                        {
                            LOGGER.debug(e.getMessage(), e);
                        }
                    }
                }

                if (cgType == null)
                {
                    try
                    {
                        final String myClassName2 = "java.lang." + myClassName;
                        final Type<?> type = Types.forName(myClassName2);

                        if (type != null)
                        {
                            cgType = createType(type);
                        }
                    }
                    catch (final TypeRecognitionException e)
                    {
                        if (LOGGER.isDebugEnabled())
                        {
                            LOGGER.debug(e.getMessage(), e);
                        }
                    }
                }
            }

            if (cgType != null)
            {
                myClassName = cgType.getName();
            }
        }

        try
        {
            answer = createType(myClassName);
        }
        catch (final TypeRecognitionException e)
        {
            try
            {
                answer = createType(className);
            }
            catch (final TypeRecognitionException e2)
            {
                System.err.println(e2.getMessage());
            }
        }

        return answer;
    }
}
